package asteroids.model;


import asteroids.Util;
import asteroids.Vector;
import be.kuleuven.cs.som.annotate.*;


/**
 * A class with bullets which have coordinates, velocities, a direction, a source, a world, a mass and a radius.
 * 
 * @author Thijs Dieltjens - Wim Konings
 * |2BCwaElt
 * |https://asteroids-ogp.googlecode.com/svn/trunk/
 * @version 2.0
 * 
 * @invar 	The bullet has valid coordinates.
 * 			|isValidCoordinate(coordinates)
 * @invar 	The bullet has a valid direction.
 * 			|isValidDirection(direction)
 * @invar	The bullet has a valid source.
 * 			|isValidSource(source)
 * @invar	The world of the bullet is the same as the world of it's source.
 * 			|Bullet.getWorld() == Bullet.getSource().getWorld()
 * @invar	The bullet has a constant radius.
 * 			|Bullet.getRadius() == RADIUS
 * @invar	The bullet has a constant mass because it has a constant radius and density.
 * 			|Bullet.getMass() == (4/3)*Math.PI*Math.pow(RADIUS, 3)*DENSITY
 */
public class Bullet extends SpaceObject {

	/**
	 * Creates a Bullet instance with given coordinates, direction, mass, world, radius and source.
	 * 
	 * @param 	coordinates
	 * 			The given coordinates of this bullet.
	 * @param	direction
	 * 			The given direction of this bullet.
	 * @param 	source
	 * 			The given source of this bullet
	 * @throws 	IllegalArgumentException
	 * 			The source isn't valid.
	 * 			| (!isValidSource(source))
	 * @effect	This new Bullet is initialized as a new SpaceObject with given coordinates and direction.
	 * 			It's mass and radius are constant and it's world is equal to the given source's world.
	 *       	| super(coordinates, velocities, (4/3)*Math.PI*Math.pow(RADIUS, 3)*DENSITY, source.getWorld(), RADIUS)
	 * @post	This bullet's source is set to the given ship.
	 * 			|new.getWorld() == source
	 */
	public Bullet(Vector coordinates, double direction, Ship source) throws IllegalArgumentException {
		super(coordinates, getVelocities(direction), (4/3)*Math.PI*Math.pow(RADIUS, 3)*DENSITY, RADIUS);
		if(isValidSource(source)) {
			this.source = source;
			setWorld(source.getWorld());
		}
		else{
			throw new IllegalArgumentException();
		}
	}

	/**
	 * Returns the vector of velocities for this bullet given a direction.
	 * 
	 * @param 	direction
	 * 			The direction of this bullet.
	 * @return	The vector of velocities of this bullet.
	 * 			| result == new Vector(BULLETSPEED*Math.cos(direction) , BULLETSPEED*Math.sin(direction))
	 * @throws 	IllegalStateException
	 * 			The velocity in one of the directions is infinite.
	 * 			| BULLETSPEED*Math.cos(direction) > Double.MAX_VALUE || 
	 * 			|		BULLETSPEED*Math.sin(direction) > Double.MAX_VALUE
	 */
	public static Vector getVelocities(double direction) throws IllegalStateException {
		Double xVelocity = BULLETSPEED*Math.cos(direction);
		Double yVelocity = BULLETSPEED*Math.sin(direction);
		if(xVelocity.isInfinite() || yVelocity.isInfinite()) {
			throw new IllegalStateException();
		}
		else {
			Vector velocities = new Vector(xVelocity, yVelocity);
			return velocities;
		}
	}

	/**
	 * Checks if the given vector of velocities of this bullet is valid.
	 * 
	 * @param 	givenVelocities
	 * 			The given velocities for this bullet.
	 * @return	True if and only if the total speed is equal to the constant BULLETSPEED.
	 * 			| result == (( Math.sqrt(Math.pow(givenVelocities.getXCoordinate(), 2) + Math.pow(givenVelocities.getYCoordinate(), 2)) == BULLETSPEED ) 
	 * 			|				&& isValidVelocity(givenVelocities)))
	 */
	public boolean canHaveAsSpeed(Vector givenVelocities) {
		double xVelocity = givenVelocities.getXCoordinate();
		double yVelocity = givenVelocities.getYCoordinate();
		double speed = Math.sqrt(Math.pow(xVelocity, 2) + Math.pow(yVelocity, 2));
		return (Util.fuzzyEquals(speed, BULLETSPEED) && canHaveAsVelocity(givenVelocities));
	}

	/**
	 * Returns the absolute speed of this bullet.
	 * 
	 * @return	The absolute speed of this bullet.
	 * 			| result > 0
	 */
	public static final double BULLETSPEED = 250.0;

	/**
	 * Returns the source of this bullet.
	 */
	@Basic @Immutable
	public Ship getSource() {
		return source;
	}

	/**
	 * Checks if the given source of this bullet is valid.
	 * 
	 * @param 	source
	 * 			The given source of this bullet.
	 * @return	True if and only is the source of this bullet isn't null. 
	 * 			| result == (source != null)
	 */
	public static boolean isValidSource(Ship source) {
		if(source == null) {
			return false;
		}
		return true;
	}

	private final Ship source;

	/**
	 * Returns true if this bullet already collided with a boundary.
	 */
	@Basic
	public boolean getCollide() {
		return collide;
	}

	/**
	 * Sets the fact that the bullet already collided with a boundary or not.
	 * 
	 * @param 	collide
	 * 			The fact that the bullet already collided with a boundary or not.
	 * @post	The inserted boolean is equal to the fact that the bullet already collided with a boundary or not.
	 * 			| new.getCollide() == collide
	 * @note	This method is written defensively.
	 */
	public void setCollide(boolean collide) {
		this.collide = collide;
	}

	/**
	 * Deletes this bullet object. 
	 * 
	 * @effect	Removes this bullet from this world's set of space objects.
	 * 			| getWorld().removeSpaceObject(this)
	 * @throws 	NullPointerException
	 * 			The world of this bullet is null.
	 * 			| getWorld()==null
	 */
	public void terminate() throws NullPointerException{
		if(getWorld() == null) {
			throw new NullPointerException();
		}
		getWorld().removeSpaceObject(this);
	}

	/**
	 * Deletes this bullet object when the bullet already bounced with a boundary.
	 * If this is the first collision with a boundary, it just bounces of it.
	 * 
	 * @effect	Removes this bullet from this world's set of space objects when the bullet already bounced with a boundary.
	 * 			If this is the first collision with a boundary, it just bounces of it and collide will be set at true.
	 * 			| if(getCollide() == true) 
	 * 			| 	then getWorld().removeSpaceObject(this)
	 * 			| else setCollide(true)
	 * @throws 	NullPointerException
	 * 			The world of this bullet is null.
	 * 			| getWorld()==null
	 */
	public void terminateBoundary() throws NullPointerException{
		if(getWorld() != null) {
			if(getCollide() == true) {
				getWorld().removeSpaceObject(this);
			}
			else {
				setCollide(true);
			}
		}
		else{
			throw new NullPointerException();
		}
	}

	public boolean collide = false;

	/**
	 * Returns the radius of this bullet.
	 * 
	 * @return	The radius of this bullet.
	 * 			| result >0
	 */
	public static final double RADIUS = 3.0;

	/**
	 * Returns the density of this bullet.
	 * 
	 * @return	The density of this bullet.
	 * 			| result > 0
	 */
	public static final double DENSITY = 7.8E12;

	@Override
	public String toString(){
		return ("Bullet with coordinates "+ getCoordinates().toString() + ", velocities "+ getVelocities().toString()+ " and source : \n"+getSource().toString());
	}
}
